package com.flower.getaway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @ClassName GatewayConfiguration
 * @Description gateway集成Sentinel的配置类
 * @Date 2021/07/30
 * @Author wuhanxiong
 */
@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 配置限流后异常处理
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public JsonSentinelGatewayBlockExceptionHandler jsonSentinelGatewayBlockExceptionHandler() {
        // //注册异常处理类给网关
        return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * Sentinel 过滤器
     * @return
     */
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }


    /**
     * 初始化限流规则
     */
    @PostConstruct
    public void initRule(){
        initCustomizedApis();
        initGatewayResourceRules();
        initDegradeRule();
    }

    /**
     * 分组资源配置
     */
    private void initCustomizedApis() {
       //分组集合
        Set<ApiDefinition> definitions = new HashSet<>();
        //创建Sentinel分组
        ApiDefinition api1 = new ApiDefinition("backstage_route")
                //设置谓词集合
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    //路径匹配
                    add(new ApiPathPredicateItem().setPattern("/discover/**")
                            //路径参数匹配策略
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    add(new ApiPathPredicateItem().setPattern("/zhifubao/**")
                            //路径参数匹配策略
                            //PARAM_MATCH_STRATEGY_EXACT,参数精确匹配
                            //PARAM_MATCH_STRATEGY_CONTAINS,参数子串匹配
                            //PARAM_MATCH_STRATEGY_PREFIX,参数前缀匹配
                            //PARAM_MATCH_STRATEGY_REGEX，参数正则匹配
                            //URL_MATCH_STRATEGY_EXACT,路径精确匹配
                            //URL_MATCH_STRATEGY_PREFIX,路径前缀匹配
                            //URL_MATCH_STRATEGY_REGEX,路径正则匹配
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        ApiDefinition api2 = new ApiDefinition("order_api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/product/baz")
                            .setMatchStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP));
                }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    /**
     * 资源限流
     */
    private void initGatewayResourceRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("backstage_route")
                .setCount(1)
                .setIntervalSec(1)
        );

        rules.add(new GatewayFlowRule("order_api")
                //限流阈值
                .setCount(1)
                //统计时间窗口，单位是秒，默认是 1 秒。
                .setIntervalSec(2)
                //应对突发请求时额外允许的请求数目。
                .setBurst(1)
                //参数限流配置
                .setParamItem(new GatewayParamFlowItem()
                        //设置从请求中提取参数的策略,有四种，分别为IP,任意Header,任意url_param参数
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
                )
        );

        rules.add(new GatewayFlowRule("backstage_route")
                .setCount(10)
                .setIntervalSec(1)
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                //匀速排队模式下的最长排队时间，单位是毫秒，仅在匀速排队模式下生效。
                .setMaxQueueingTimeoutMs(600)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
                        //若提取策略选择 Header 模式或 URL 参数模式，则需要指定对应的 header 名称或 URL 参数名称。
                        .setFieldName("X-Sentinel-Flag")
                )
        );
        rules.add(new GatewayFlowRule("backstage_route")
                .setCount(1)
                .setIntervalSec(1)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("pa")
                )
        );
      rules.add(new GatewayFlowRule("order_api")
                .setCount(2)
                .setIntervalSec(30)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("type")
                        //参数值的匹配模式，只有匹配该模式的请求属性值会纳入统计和流控；若为空则统计该请求属性的所有值.
                        .setPattern("warn")
                        .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
                )
        );

        rules.add(new GatewayFlowRule("pay_api")
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
                .setCount(5)
                .setIntervalSec(1)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("pn")
                )
        );
        GatewayRuleManager.loadRules(rules);

    }

    /**
     * 熔断降级
     */
        private  void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        //设置熔断资源b
        DegradeRule rule = new DegradeRule("backstage_route")
        //设置熔断策略，策略有慢调用比例(SLOW_REQUEST_RATIO),异常比例(ERROR_RATIO),异常数 (ERROR_COUNT)
        .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
        //慢调用比例模式下为慢调用临界 RT（超出该值计为慢调用）；异常比例/异常数模式下为对应的阈值
        .setCount(0.6)
        //熔断触发的最小请求数，请求数小于该值时即使异常比率超出阈值也不会熔断
        .setMinRequestAmount(10)
                 //统计时长（单位为 ms），如 60*1000 代表分钟级
                .setStatIntervalMs(30000) // 30s
                //熔断时长，单位为 s
                .setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

}
